#include <iostream>
#include "rt.hh"
#include "color.hh"
#include "hittable_list.hh"
#include "sphere.hh"
#include "camera.hh"
#include "material.hh"

color ray_color(const ray& r, const hittable& world, int depth) {
	if (depth <= 0) { return color{0, 0, 0}; }

	hit_record rec;
	if (world.hit(r, 0.001, infinity, rec)) {
		ray scattered;
		color attenuation;

		if (rec.pmat->scatter(r, rec, attenuation, scattered)) {
			return attenuation * ray_color(scattered, world, depth - 1);	
		}

		return color{0, 0, 0};
	}

	vec3 udir = unit_vec(r.direction());
	auto t = 0.5 * (udir.y() + 1.0);
	return (1.0 - t) * color{1.0, 1.0, 1.0} + t * color{0.5, 0.7, 1.0};
}

hittable_list rand_world() {
	hittable_list world;
	auto mat_ground = make_shared<lambertion>(color{0.5, 0.5, 0.5});
	world.add(make_shared<sphere>(point3{0, -1000, 0}, 1000, mat_ground));

	for (int a = -11; a < 11; a++) {
		for (int b = -11; b < 11; b++) {
			auto choose_mat = rand_double();
			point3 center{a + 0.9 * rand_double(), 0.2, b + 0.9 * rand_double()};

			if ((center - point3{4, 0.2, 0}).len() > 0.9) {
				shared_ptr<material> mat;	

				if (choose_mat < 0.8) {
					auto albedo = color::rand() * color::rand();
					mat = make_shared<lambertion>(albedo);
				world.add(make_shared<sphere>(center, 0.2, mat));
				}
				else if (choose_mat < 0.95) {
					auto albedo = color::rand(0.5, 1.0);
					auto fuzz = rand_double(0.0, 0.5);
					mat = make_shared<metal>(albedo, fuzz);
				world.add(make_shared<sphere>(center, 0.2, mat));
				}
				else {
					mat = make_shared<dielectric>(1.5);
				world.add(make_shared<sphere>(center, 0.2, mat));
				}

			}
		}
	}

    auto material1 = make_shared<dielectric>(1.5);
    world.add(make_shared<sphere>(point3{0, 1, 0}, 1.0, material1));

    auto material2 = make_shared<lambertion>(color{0.4, 0.2, 0.1});
    world.add(make_shared<sphere>(point3{-4, 1, 0}, 1.0, material2));

    auto material3 = make_shared<metal>(color{0.7, 0.6, 0.5}, 0.0);
    world.add(make_shared<sphere>(point3{4, 1, 0}, 1.0, material3));
	
	return world;
}

int main() { 
	// windows
	const double win_ratio = 3.0 / 2.0; 
	const int width  = 1200;
	const int height = static_cast<int>(width / win_ratio);	
	const int samples_per_pixel = 100;
	const int max_depth = 50;
	
	//world 
	hittable_list world = rand_world();

	// camera
	point3 lookfrom{13, 2, 3};
	point3 lookat{0, 0, 0};
	vec3 vup{0, 1, 0};
	auto aperture = 0.1;
	auto focus_dist = 10.0;

	camera cam(lookfrom, lookat, vup, 20, win_ratio, aperture, focus_dist);

	// render 
	std::cout << "P3\n" << width << " " << height << "\n255\n";

	for (int i = height - 1; i >= 0; i--) { 
		std::cerr << "\rScanline remaining : " << i << std::flush;
		for (int j = 0; j < width; j++) { 
			color pixel_color{0, 0, 0};
			for (int k = 0; k < samples_per_pixel; k++) {
				auto h = (double(j) + rand_double()) / (width  - 1);
				auto v = (double(i) + rand_double()) / (height - 1);

				ray r = cam.get_ray(h, v);
				pixel_color += ray_color(r, world, max_depth);
			}
			
			write_color(std::cout, pixel_color, samples_per_pixel);
		} 
	}

	std::cerr << "\ndone\n" << std::flush;
	return 0;
}
